<?php

/**
 * Combined database and filesystem storage for emails.
 * Will add mail headers and their text content (plain + html).
 * Attachment names and content types will also be stored in the
 * database, the attachments' contents will be placed in the file
 * system.
 *
 * @author tobias.vogel
 */
class Mailfetcher_DbFsStorage {
	
	/**
	 * @var array Messages from storing
	 */
	public $messages = array();

	/**
	 * Stores multiple messages.
	 * 
	 * @param Db_Mailbox $mailbox The mailbox the messages are from
	 * @param array $messages The messages to store
	 */
	public function storeMessages($mailbox, $messages) {

		$storedMessages = 0;

		foreach ($messages as $message) {
			/* @var ezcMail $message */
			$result = $this->storeMessage($mailbox, $message);
			$storedMessages += $result;
		}

		$this->messages[] = "Stored {$storedMessages} messages";
	}

	/**
	 * Stores a single mail object.
	 * 
	 * @param Db_Mailbox $mailbox The mailbox the messages are from
	 * @param ezcMail $message The message to store
	 */
	public function storeMessage($mailbox, $message) {

		$search = array(
			'mbx_mailbox_id' => $mailbox->id,
			'ml_message_id' => $message->messageId
		);

		$mail = Db_Mails::findByFields($search);
		
		if (!$mail) {
			$mail = new Db_Mails();
			$mail->mbx_mailbox_id = $mailbox->id;
			$mail->ml_subject = $message->subject;
			$mail->ml_from = $this->formatAddress($message->from);
			$mail->ml_to = $this->formatAddresses($message->to);
			$mail->ml_cc = $this->formatAddresses($message->cc);
			$mail->ml_datetime = date('Y-m-d H:i:s', $message->timestamp);
			$mail->ml_message_id = $message->messageId;
			$mail->ml_references = $message->getHeader('References');
			$mail->ml_in_reply_to = $message->getHeader('In-Reply-To');

			$mail->save();

			$this->storeMimeParts($message, $mail);
			return 1;
		}
		return 0;
	}

	/**
	 * Formats multiple email adress with owner name and address in angle brackets,
	 * each address separated by comma.
	 * 
	 * @param array $addresses Array of email addresses
	 * @return string Formatted addresses, separated by comma 
	 */
	private function formatAddresses($addresses) {
		$fa = array();
		foreach ($addresses as $address) {
			/* @var $address ezcMailAddress */
			$fa[] = $this->formatAddress($address);
		}
		return implode(', ', $fa);
	}

	/**
	 * Formats an email adress with owner name and address in angle brackets.
	 * 
	 * @param ezcMailAddress $address The address to format
	 * @return string Formatted address
	 */
	private function formatAddress($address) {
		$name = '';
		if (!empty($address->name)) {
			$name = "{$address->name} ";
		}
		if (!empty($address->email)) {
			$name .= "<{$address->email}>";
		}
		return $name;
	}

	/**
	 * Stores all MIME parts of an email.
	 * 
	 * @param ezcMail $message The message to parse for mime parts-
	 * @param Db_Mails $mail Existing mail entry containing the mail headers
	 */
	private function storeMimeParts($message, $mail) {

		$bodyParts = $message->fetchParts(array('ezcMailText'));

		foreach ($bodyParts as $part) {

			if ($part->subType === 'plain') {
				$mail->ml_body_plain = $part->text;
			} else if ($part->subType === 'html') {
				$mail->ml_body_html = $part->text;
			} else {
				$this->storeBodyAttachment($mail, $part);
			}
		}

		$mail->update();

		$attParts = $message->fetchParts(array('ezcMailFile'));
		foreach ($attParts as $part) {
			$this->storeFileAttachment($mail, $part);
		}
	}

	/**
	 * Stores a plain text attachment of an email
	 * 
	 * @param Db_Mails $mail The email to assign the attachment to
	 * @param ezcMailText $part The plain text part to store
	 */
	private function storeBodyAttachment($mail, $part) {
		$fileName = 'noname';

		if (isset($part->contentDisposition->displayFileName)) {
			$fileName = $part->contentDisposition->displayFileName;
		} else if (isset($part->contentDisposition->fileName)) {
			$fileName = $part->contentDisposition->fileName;
		}
		
		$this->storeAttachment($mail, $fileName, $part->text, 'text', $part->subType);
	}

	/**
	 * Stores a file attachment of an email
	 * 
	 * @param Db_Mails $mail The email to assign the attachment to
	 * @param ezcMailFilePart $part The part of the mail to store
	 */
	private function storeFileAttachment($mail, $part) {
		$fileName = isset($part->contentDisposition->displayFileName) ?
			$part->contentDisposition->displayFileName :
			basename($part->fileName);

		$content = file_get_contents($part->fileName);
		
		$this->storeAttachment($mail, $fileName, $content, $part->contentType, $part->mimeType);
	}
	
	/**
	 * Stores an attachment for an email
	 * 
	 * @param ezcMail $mail The mail the attachment belongs to
	 * @param string $fileName The filename of the attachment
	 * @param string $content File content
	 * @param string $contentType Content type of the attachment
	 * @param string $mimeType MIME type of the content
	 */
	private function storeAttachment($mail, $fileName, $content, $contentType, $mimeType) {
		$checksum = md5($content);
		
		$search = array(
			'att_checksum' => $checksum,
			'att_name' => $fileName
		);

		// search existing file
		$attachment = Db_Attachments::findByFields($search);

		// create new if not found
		if (!$attachment) {
			$attachment = new Db_Attachments();
			$attachment->att_checksum = $checksum;
			$attachment->att_name = $fileName;

			$attachment->att_contenttype = $contentType;
			$attachment->att_mimetype = $mimeType;

			$diskFile = $attachment->getDiskFileName();
			file_put_contents($diskFile, $content);
			touch($diskFile, strtotime($mail->ml_datetime));

			$attachment->save();
		}

		$attachment->assignToMail($mail);		
	}

}
